﻿using System;
using System.Collections.Generic;
using System.Linq;
using NiuX.Utils.DependencyInjection.Attributes;

namespace NiuX.Utils.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IEnumerable<T> GetServices<T>(this ServiceContainer services)
    {
        return services.GetService<IEnumerable<T>>();
    }

    public static T GetService<T>(this ServiceContainer services) => (T)services.GetService(typeof(T));

    public static bool HasRegistry<T>(this ServiceContainer services) => services.HasRegistry(typeof(T));

    public static bool HasRegistry(this ServiceContainer services, Type serviceType) => services.Root.Registries.ContainsKey(serviceType);

    public static ServiceContainer Register(this ServiceContainer services, Type from, Type to, LifetimeType lifetimeType)
    {
        services.Register(new ServiceRegistry(from, lifetimeType, (_, arguments) => Create(_, to, arguments)));
        return services;
    }

    public static ServiceContainer Register<TFrom, TTo>(this ServiceContainer services, LifetimeType lifetimeType) where TTo : TFrom
    {
        services.Register(new ServiceRegistry(typeof(TFrom), lifetimeType, (_, arguments) => Create(_, typeof(TTo), arguments)));
        return services;
    }

    public static ServiceContainer Register<TServiceType>(this ServiceContainer services, Func<ServiceContainer, TServiceType> factory, LifetimeType lifetimeType = LifetimeType.Singlelton)
    {
        services.Register(new ServiceRegistry(typeof(TServiceType), lifetimeType, (_, __) => factory(_)));
        return services;
    }

    public static ServiceContainer RegisterInstance<TServiceInstace>(this ServiceContainer services, LifetimeType lifetimeType) where TServiceInstace : new()
    {
        services.Register(new ServiceRegistry(typeof(TServiceInstace), lifetimeType, (_, __) => new TServiceInstace()));
        return services;
    }

    public static ServiceContainer RegisterSinglelton<TServiceInstace>(this ServiceContainer services, TServiceInstace instance)
    {
        services.Register(new ServiceRegistry(typeof(TServiceInstace), LifetimeType.Singlelton, (_, __) => instance!));
        return services;
    }

    public static ServiceContainer CreateChild(this ServiceContainer services) => new(services);

    private static object Create(ServiceContainer services, Type type, Type[] genericArgumnents)
    {
        if (genericArgumnents.Length > 0)
        {
            type = type.MakeGenericType(genericArgumnents);
        }

        var constructors = type.GetConstructors();

        if (constructors.Length == 0)
        {
            throw new InvalidOperationException($"Cannot create the instance of {type} which does not have an public constructor.");
        }

        var constructor = constructors.FirstOrDefault(x => x.GetCustomAttributes(false).OfType<InjectionAttribute>().Any());
        constructor = constructor ?? constructors.First();

        var parameters = constructor.GetParameters();

        if (parameters.Length == 0)
        {
            return Activator.CreateInstance(type);
        }

        var arguments = new object[parameters.Length];
        for (int index = 0; index < arguments.Length; index++)
        {
            var parameter = parameters[index];
            var parameterType = parameter.ParameterType;

            if (services.HasRegistry(parameterType))
            {
                arguments[index] = services.GetService(parameterType);
            }
            else if (parameter.HasDefaultValue)
            {
                arguments[index] = parameter.DefaultValue;
            }
            else
            {
                throw new InvalidOperationException($"Cannot create the instance of {type} whose constructor has non-registered parameter type(s)");
            }
        }

        return Activator.CreateInstance(type, arguments);
    }
}